﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Optimizer
{
    /// <summary>
    /// Синтез детерминированного и стохастического алгоритмов поиска 
    /// глобального экстремума с обучением.
    /// </summary>
    class Mixed: IOptimizer
    {
        public string Name()
        {
            return "Глобальний";
        }

        double Radius = 1; //начальный радиус поиска
        const double Kappa = 0.618; //коэффициент стягивания

        /// <summary>
        /// параметры масштаба
        /// </summary>
        readonly Point Lambda = new Point()
        {
            X1 = 1,
            X2 = 1,
        };

        const double N = 100; //время "обучения"
        const double Epsilon = 0.01; //0.000001; //точность вычисления
        const int M = 5; //количество строк "обучающей" матрицы

        /// <summary>
        /// координаты начальной точки
        /// </summary>
        Point X0 = new Point()
        {
            X1 = 0.5,
            X2 = 0.5,
        };

        /// <summary>
        /// "обучающая" матрица
        /// </summary>
        FPoint[] PreHistory = new FPoint[M];

        /// <summary>
        /// угол при вершине конуса
        /// </summary>
        double Alpha = Math.PI / 6;

        Point Xdet;
        double PhiDet;

        private Random R = new Random();

        public Point Do(Func<Point, double> f)
        {
            Point XPred;
            this.Go(f);
            do
            {
                XPred = PreHistory[M-1].X;
                this.Go(f);
            } while ((PreHistory[M - 1].X - XPred).Length() > Epsilon);
            return this.PreHistory[M - 1].X;
        }

        private void Go(Func<Point, double> f)
        {
            Radius = 1;
            this.FormCenter(f);
            this.FormMatrix(f);
            // Первый раз нам не интересен результат детерменированного поиска.
            this.Det(f);
            while (Radius > Epsilon)
            {
                var bStoch = this.Stoch(f);
                var bDet = this.Det(f);
                if (!(bDet && bStoch))
                {
                    // стягивание если ни стохастический ни детерменированный 
                    // не дали результат.
                    Radius *= Kappa;
                }
            }
        }

        private double NextRandom()
        {
            return 2 * this.R.NextDouble() - 1;
        }

        private Point NextRandomPoint()
        {
          return
                 Radius *
                 new Point()
                 {
                     X1 = NextRandom(),
                     X2 = NextRandom(),
                 };
        }

        /// <summary>
        /// модуль формирования центра поиска
        /// </summary>
        private void FormCenter(Func<Point, double> f)
        {
            //значение в начальной точке
            double Phi0 = f(this.X0);
            // координаты пробного центра
            var XN = this.X0 + this.NextRandomPoint();
            double Phi = f(XN);
            while (Phi < Phi0)
            {
                this.X0 = XN;
                Phi0 = Phi;
                XN = this.X0 + this.NextRandomPoint();
                Phi = f(XN);
            }
        }

        /// <summary>
        /// модуль формирования "обучающей" матрицы
        /// </summary>
        private void FormMatrix(Func<Point, double> f)
        {
            double Phi;
            double Phi0 = f(this.X0); //значение в начальной точке

            for (int i = 0; i < M - 1; i++)
            {
                PreHistory[i] = new FPoint()
                {
                    X = this.X0,
                    Y = double.MaxValue,
                };
            }
            PreHistory[M - 1] = new FPoint()
            {
                X = this.X0,
                Y = Phi0,
            };

            for (int i = 0; i < N; i++)
            {
                this.X0 = PreHistory[0].X + this.NextRandomPoint();
                Phi = f(X0);
                this.CorrectMatrix(this.X0, Phi);
            }
        }

        /// <summary>
        /// Модуль детерминированого поиска.
        /// </summary>
        /// <returns>true если результат поиска улучшился, иначе false.</returns>
        private bool Det(Func<Point, double> f)
        {
            bool Change = false;
            double[] Koef = {0.6, -0.6, -0.8, 0, 1.8};
            this.Xdet = new Point();
            for (int i = 0; i < M; i++)
            {
                this.Xdet += Koef[i] * this.PreHistory[i].X;
            }
            PhiDet = f(this.Xdet);
            if (PhiDet < PreHistory[0].Y)
            {
                Change = true;
            }
            while (PhiDet < PreHistory[0].Y)
            {
                this.CorrectMatrix(this.Xdet, PhiDet);
                this.Xdet = new Point();
                for (int i = 0; i < M; i++)
                {
                    this.Xdet += Koef[i] * PreHistory[i].X;
                }
                PhiDet = f(this.Xdet);
            }
            return Change;
        }

        /// <summary>
        /// Модуль случайного поиска.
        /// </summary>
        /// <returns>true если результат поиска улучшился, иначе false.</returns>
        private bool Stoch(Func<Point, double> f)
        {
            //double Rad = Radius / 2;
            double Ro = Radius * Math.Sin(Alpha);
            bool Change = false;
            var W = PreHistory[M - 1].X - this.Xdet;
  
            var XVR = PreHistory[M - 1].X + Radius * W.Norm();

            var DeltaXVR =
                XVR + 
                Math.Sin(Alpha) * this.NextRandomPoint().Norm() - 
                PreHistory[M - 1].X;
            this.X0 = PreHistory[M - 1].X + Radius * DeltaXVR.Norm();

            double Phi = f(this.X0);
            if (Phi < PreHistory[0].Y)
            {
                CorrectMatrix(this.X0, Phi);
                Change = true;
                for (int i = 0; i < N; i++)
                {
                    this.X0 = XVR + this.NextRandomPoint();
                    Phi = f(this.X0);
                    CorrectMatrix(this.X0, Phi);
                }
            }
            return Change;
        }

        /// <summary>
        /// корректировка матрицы
        /// </summary>
        /// <param name="a"></param>
        /// <param name="aPhi"></param>
        private void CorrectMatrix(Point a, double aPhi)
        {
            if (aPhi < PreHistory[0].Y)
            {
                PreHistory[0].X = a;
                PreHistory[0].Y = aPhi;
                for (int j = 1; j < M; j++)
                {
                    if (aPhi < PreHistory[j].Y)
                    {
                        PreHistory[j - 1] = PreHistory[j];
                        PreHistory[j].X = a;
                        PreHistory[j].Y = aPhi;
                    }
                }
            }            
        }
    }
}